package it.infodea.tapestrydea.services.jcr.nodes.wrappers;

import it.infodea.tapestrydea.utils.FileUtil;
import it.infodea.tapestrydea.utils.JcrDeaConstants;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;

import org.apache.jackrabbit.value.StringValue;

/**
 * Classe wrapper per i nodi di tipo nt:folder. Oltre a consentire l'accesso
 * alle principali proprietà del nodo, la classe mette a disposizione diversi
 * metodi utili per reperire i files in esso contenuti, o le carelle childrens,
 * verificare la presenza di un certo child (file o folder), o di crearne uno
 * nuovo.
 * 
 * @author bobpuley
 * 
 */
public class JcrFolderNode extends JcrAbstractNode {

	/**
	 * @see JcrAbstractNode#AbstractNodeDTO(Node)
	 */
	public JcrFolderNode(Node surroundedNode) throws RepositoryException {
		super(surroundedNode);
	}

	/**
	 * Restituisce la lista di childs folders della carella corrente.
	 * L'ordinamento non è garantito, se occorre assumenrne uno specifico si può
	 * farlo direttamente sulla <code>List</code>
	 * 
	 * @return List<FolderNodeDTO> la lista delle cartelle figlie del nodo
	 *         corrente. Se non ce ne sono restituisce una lista vuota.
	 * @throws RepositoryException
	 */
	public List<JcrFolderNode> getChilds() throws RepositoryException {
		List<JcrFolderNode> results = new ArrayList<JcrFolderNode>();
		NodeIterator nodes = this.surroundedNode.getNodes();
		// itera sui nodi finchè ce ne sono
		while (nodes.hasNext()) {
			Node nextNode = nodes.nextNode();
			// se è un nodo di systema salta all'elemento successivo
			// TODO [Marco]: verificare la possibilità di eliminare questo controllo
			if (isSystemNode(nextNode))
				continue;
			// se il nodo è di tipo folder lo aggiunge alla lista
			if (isFolder(nextNode)) {
				results.add(new JcrFolderNode(nextNode));
			}
		}
		return results;
	}

	/**
	 * Crea un nuovo nodo file nella carella corrente impostando nome e dati
	 * binari secondo i parametri ricevuti. Il name non deve comprendere
	 * caratteri non permessi (es: <b>/</b>) TODO [Marco]: linkare una risorsa
	 * che dia l'elenco di quelli validi.
	 * 
	 * @param name
	 *            il nome col quale il nodo file verrà salvato sul repository
	 * @param owner
	 *            il nome del proprietario del file (se null la proprietà non viene settata)
	 * @param stream
	 *            lo stram di dati binari, non deve essere null, e deve essere
	 *            aperto. Dato che lo stream non viene chiuso al termine
	 *            dell'operazione, il chiamante dovrà gestirlo da se.
	 * @return FileNodeDTO restituisce il wrapper del nodo creato.
	 * @throws RepositoryException
	 *             se si verifica un errore durante la creazione e
	 *             inizializzazione del nuovo nodo.
	 */
	public JcrFileNode createFile(String name, String owner, InputStream stream) throws RepositoryException {
		// aggiunge il nodo dea:file col nome name al nodo corrente
		Node newFile = this.surroundedNode.addNode(name, JcrDeaConstants.FILE_PRIMARY_TYPE);
		if (owner != null) {
			// imposta il nome del proprietario del file
			newFile.setProperty(JcrDeaConstants.DEA_OWNER, new StringValue(owner));
		}
		// lo imposta come versionabile
		newFile.addMixin("mix:versionable");
		// aggiunge al nodo file il child node content di tipo nt:resource per i dati binari
		Node content = newFile.addNode(JcrDeaConstants.JCR_CONTENT, "nt:resource");
		//reperisce e imposta il mimeType sulla base del nome (estensione) del file
		String mimeType = FileUtil.getMimeType(name);
		content.setProperty(JcrDeaConstants.JCR_MIME_TYPE, mimeType);
		//TODO [Marco]: gestire l'encoding
		content.setProperty(JcrDeaConstants.JCR_ENCODING, "UTF-8");
		// imposta i dati binari
		content.setProperty(JcrDeaConstants.JCR_DATA, stream);
		Calendar lastModified = Calendar.getInstance();
		// imposta la data di creazione
		content.setProperty(JcrDeaConstants.JCR_LAST_MODIFIED, lastModified);

		jcrSession.save();
		// crea la prima versione del file.
		newFile.checkin();
		return new JcrFileNode(newFile);
	}

	/**
	 * Crea una nuova cartella allinterno di quella corrente
	 * 
	 * @param name
	 *            il nome della nuova cartella
	 * @return FolderNodeDTO la cartella appena creata
	 * @throws RepositoryException
	 */
	public JcrFolderNode createFolder(String name) throws RepositoryException {
		Node newFolder = this.surroundedNode.addNode(name, JcrDeaConstants.FOLDER_PRIMARY_TYPE);
		newFolder.addMixin("mix:referenceable");
		jcrSession.save();
		return new JcrFolderNode(newFolder);
	}

	/**
	 * Restituisce una lista di nodi file contenuta nella caretlla corrente Se
	 * non ce ne sono restituisce una lista vuota
	 * 
	 * @return List<FileNodeDTO> i files della carella corrente
	 * @throws RepositoryException
	 */
	public List<JcrFileNode> getFiles() throws RepositoryException {
		List<JcrFileNode> results = new ArrayList<JcrFileNode>();
		NodeIterator nodes = this.surroundedNode.getNodes();
		while (nodes.hasNext()) {
			Node nextNode = nodes.nextNode();
			if (isSystemNode(nextNode))
				continue;
			if (isFile(nextNode)) {
				results.add(new JcrFileNode(nextNode));
			}
		}
		return results;
	}

	/**
	 * Restituisce il parent node (cartella parent) o null se quella corrente è
	 * la root
	 * 
	 * @return FolderNodeDTO la cartella parent o null
	 * @throws RepositoryException
	 */
	public JcrFolderNode getParent() throws RepositoryException {
		JcrFolderNode parent = null;
		if (!isRoot()) {
			Node nodeParent = this.surroundedNode.getParent();
			if (JCR_ROOT_PATH.equals(nodeParent.getPath())) {
				parent = new JcrRootFolderNode(nodeParent);
			} else {
				parent = new JcrFolderNode(nodeParent);
			}
		}
		return parent;
	}

	/**
	 * Se una cartella con un determinato nome esiste all'interno della carella
	 * corrente, allora viene caricata e restituita al chiamante
	 * 
	 * @param name
	 *            il nome della cartella richiesta
	 * @return FolderNodeDTO la cartella richiesta o null se non è presente
	 *         all'interno di quella corrente.
	 * @throws RepositoryException
	 */
	public JcrFolderNode getFolder(String name) throws RepositoryException {
		return contains(name) ? new JcrFolderNode(this.surroundedNode.getNode(name)) : null;
	}

	/**
	 * Se dentro la cartella corrente esiste il file richiesto, allora questo
	 * viene caricato e restituito al chiamante
	 * 
	 * @param name
	 *            il nome del file desiderato
	 * @return FileNodeDTO il file richiesto o null se non è presente
	 *         all'interno della cartella corrente
	 * @throws RepositoryException
	 */
	public JcrFileNode getFile(String name) throws RepositoryException {
		return contains(name) ? new JcrFileNode(this.surroundedNode.getNode(name)) : null;
	}

	private boolean isSystemNode(Node nextNode) throws ValueFormatException, PathNotFoundException, RepositoryException {
		boolean isSystemNode = false;
		if (nextNode.hasProperty(JcrDeaConstants.JCR_PRIMARY_TYPE_ATTR)) {
			String primaryType = nextNode.getProperty(JcrDeaConstants.JCR_PRIMARY_TYPE_ATTR).getString();
			isSystemNode = JcrDeaConstants.SYSTEM_PRIMARY_TYPE.equals(primaryType);
		}
		return isSystemNode;
	}

	private boolean isFile(Node nextNode) throws ValueFormatException, PathNotFoundException, RepositoryException {
		boolean isFile = false;
		if (nextNode.hasProperty(JcrDeaConstants.JCR_PRIMARY_TYPE_ATTR)) {
			String primaryType = nextNode.getProperty(JcrDeaConstants.JCR_PRIMARY_TYPE_ATTR).getString();
			isFile = JcrDeaConstants.FILE_PRIMARY_TYPE.equals(primaryType);
		}
		return isFile;
	}

	private boolean isFolder(Node nextNode) throws PathNotFoundException, RepositoryException {
		boolean isFolder = false;
		if (nextNode.hasProperty(JcrDeaConstants.JCR_PRIMARY_TYPE_ATTR)) {
			String primaryType = nextNode.getProperty(JcrDeaConstants.JCR_PRIMARY_TYPE_ATTR).getString();
			isFolder = JcrDeaConstants.FOLDER_PRIMARY_TYPE.equals(primaryType);
		}
		return isFolder;
	}

}
